<?php
// +------------------------------------------------------------------
// | Cls-Token [ USER LOGIN AUTHENTICATION TOKEN ]
// | Copyright (c) 2024 All rights reserved.
// | Based on ThinkPHP 6 | 8
// | Licensed Mulan PSL v2 ( http://license.coscl.org.cn/MulanPSL2 )
// | Author: CLS <422064377>
// | CreateDate: 2024/7/30
// +------------------------------------------------------------------

namespace chleniang\ClsToken\command;


use Phinx\Util\Util as PhinxUtil;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\helper\Str;

class CreateDbTable extends Command
{
    protected static string $tpl_file_create_token_table = __DIR__ . DIRECTORY_SEPARATOR . 'stub' . DIRECTORY_SEPARATOR . 'CreateTokenTable.php.tpl';

    protected function configure()
    {
        $this->setName("token:create-db-table")
            ->setDescription("创建token表数据库迁移文件")
            ->addUsage(sprintf("<comment>%s  > 此命令创建数据库迁移文件，需要配合migrate命令完成最终数据库表的创建!%s  > 只有在使用数据库保存token记录时才需要创建token表!%s  > 最终token表会使用默认数据库连接创建。%s</comment>  ", PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL))
            ->setHelp(sprintf('%s<comment>  > 创建不启用刷新令牌的"user_token1"表:</comment>%s<info>    php think token:create-db-table user_token1 </info>%s%s<comment>  > 创建启用刷新令牌的"user_token2"表:</comment>%s<info>    php think token:create-db-table user_token2 --refresh-token </info>%s%s<comment>  > 强制创建启用刷新令牌的"user_token3"表:</comment>%s<info>    php think token:create-db-table user_token3 -r -f </info>%s', PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL))
            ->addArgument("dbname", Argument::REQUIRED, "待创建的token数据表名(不含前缀).")
            ->addOption("refresh-token", "r", Option::VALUE_NONE, "启用刷新令牌(启用会对该字段添加索引)")
            ->addOption("force", "f", Option::VALUE_NONE, "强制创建表(如果存在同名表则删除先)" . PHP_EOL);
    }

    protected function execute(Input $input, Output $output)
    {
        $dbname = $input->getArgument("dbname");
        if (empty($dbname)) {
            throw new \InvalidArgumentException("<dbname> Empty.");
        }

        // Option::VALUE_NONE 选项的值:  true / false
        $refresh = $input->getOption("refresh-token");
        $force   = $input->getOption("force");

        $refreshIndexCode = '';
        $dropTableCode    = '';

        if ($refresh) {
            $refreshIndexCode = <<<EOS
// ===== REFRESH_INDEX [START] =====
->addIndex(
    "refresh_token",
    [
        'name' => 'refresh_token_idx',
        // 'unique' => true,
    ]
)
// ===== REFRESH_INDEX [END] =====
EOS;
        }
        if ($force) {
            $dropTableCode = <<<EOS
// ===== DROP_TABLE [START] =====
\$hasTable = \$this->hasTable('{$dbname}');
if (\$hasTable) {
    \$this->table('{$dbname}')
        ->drop()
        ->save();
}
// ===== DROP_TABLE [END] =====
EOS;
        }

        $migrateFileContent = file_get_contents(self::$tpl_file_create_token_table);
        if (empty($migrateFileContent)) {
            throw new \ErrorException("Template file \"CreateTokenTable.php.tpl\" does not exist.");
        }

        $className = self::buildClassName($dbname);
        if (!PhinxUtil::isValidPhinxClassName($className)) {
            throw new \InvalidArgumentException(sprintf("表名(%s)不符合规范[多个单词用下划线连接]", $dbname));
        }

        $migrateBasePath = $this->app->getRootPath() . 'database' . DIRECTORY_SEPARATOR . 'migrations';
        if (!PhinxUtil::isUniqueMigrationClassName($className, $migrateBasePath)) {
            $snake = function ($matches) {
                return '_' . strtolower($matches[0]);
            };
            // _admin_token_4   类似这样的文件名 不含 时间戳 以及 .php
            $tmpFileName  = preg_replace_callback('/\d+|[A-Z]/', $snake, $className);
            $allFile      = scandir($migrateBasePath);
            $hasExistFile = '[timestamp]' . $tmpFileName . '.php';
            if ($allFile) {
                $pattern = '/\d+' . preg_quote($tmpFileName, '_') . '\.php/i';
                foreach ($allFile as $f) {
                    if (preg_match($pattern, $f)) {
                        $hasExistFile = $f;
                        break;
                    }
                }
            }
            throw new \InvalidArgumentException(sprintf("该表(%s)已存在相应的迁移文件" . PHP_EOL . " >>> %s；" . PHP_EOL . "可使用migrate相关命令查看状态，回滚并删除相应迁移文件后重试。", $dbname, $hasExistFile));
        }

        $migrateFileName = PhinxUtil::mapClassNameToFileName($className);
        if (!PhinxUtil::isValidMigrationFileName($migrateFileName)) {
            throw new \InvalidArgumentException(sprintf("生成的迁移文件名(%s)不符合规范,请重试.", $migrateFileName));
        }

        $migrateFileContent = strtr(
            $migrateFileContent,
            [
                "##TABLE_NAME##"    => $dbname,
                "##REFRESH_INDEX##" => $refreshIndexCode,
                '##DROP_TABLE##'    => $dropTableCode,
                '##CLASS_NAME##'    => $className,
            ]
        );

        if (!is_dir($migrateBasePath)) {
            if (!mkdir($migrateBasePath, 0755, true) && !is_dir($migrateBasePath)) {
                throw new \RuntimeException(sprintf('Migration目录 "%s" 创建失败,请手动创建,并保证其有写权限.', $migrateBasePath));
            }
        }
        elseif (!is_writable($migrateBasePath)) {
            throw new \RuntimeException(sprintf('Migration目录 "%s" 无法写入文件,请修改为"可写"权限后重试.', $migrateBasePath));
        }

        // ======= 压缩代码 [START]=======

        // $codeTokens = token_get_all($migrateFileContent, TOKEN_PARSE);
        //
        // $compressContent = '';
        // $lastTag         = -99;
        // foreach ($codeTokens as $token) {
        //     if (is_array($token)) {
        //         switch ($token[0]) {
        //             case T_BAD_CHARACTER:
        //             case T_COMMENT:
        //             case T_DOC_COMMENT:
        //             case T_WHITESPACE:
        //                 break;
        //             default:
        //                 if ($lastTag === T_WHITESPACE) {
        //                     $compressContent .= ' ' . $token[1];
        //                 }
        //                 else {
        //
        //                     $compressContent .= $token[1];
        //                 }
        //                 break;
        //         }
        //         $lastTag = $token[0];
        //     }
        //     elseif (is_string($token)) {
        //         $lastTag         = -99;
        //         $compressContent .= $token;
        //     }
        //     else {
        //         $lastTag = -99;
        //     }
        //
        // }
        // $inputFile = $migrateBasePath . DIRECTORY_SEPARATOR . $migrateFileName;
        // if (file_put_contents($inputFile, $compressContent) === false) {
        //     throw new \RuntimeException(sprintf('Migration文件 "%s" 写入失败.', $inputFile));
        // }

        // ======= 压缩代码 [END]=======

        $inputFile = $migrateBasePath . DIRECTORY_SEPARATOR . $migrateFileName;
        if (file_put_contents($inputFile, $migrateFileContent) === false) {
            throw new \RuntimeException(sprintf('Migration文件 "%s" 写入失败.', $inputFile));
        }

        $version = PhinxUtil::getVersionFromFileName($migrateFileName);

        $this->output->writeln('<comment> >>>---------------------- </comment>');
        $this->output->writeln("<comment> ^v^ 数据库迁移文件创建成功! </comment>");
        $this->output->writeln("<comment> [ <info>{$inputFile}</info> ] </comment>" . PHP_EOL);
        $this->output->writeln("<comment> == 请执行以下命令完成最终数据库更新.</comment>" . PHP_EOL);
        $this->output->writeln('<comment> > 查看migration状态:</comment>');
        $this->output->writeln('  <info> php think migrate:status </info>' . PHP_EOL);
        $this->output->writeln('<comment> > 执行迁移(所有待迁移版本全部执行):</comment>');
        $this->output->writeln('  <info> php think migrate:run </info>' . PHP_EOL);
        $this->output->writeln('<comment> > 或者指定版本号执行迁移:</comment>');
        $this->output->writeln("  <info> php think migrate:run -t {$version} </info>" . PHP_EOL);
    }

    /**
     * 通过数据表名生成类名(固定前缀"CreateTokenTable")
     *      示例: 表名 user_token  => CreateTokenTableUserToken
     *
     * @param string $tableName 数据表名
     *
     * @return string
     */
    protected static function buildClassName(string $tableName)
    {
        return 'CreateTokenTable' . Str::studly(Str::camel($tableName));
    }

}